/****************************************Copyright (c)**************************************************
** File name:		    tranfile.c
** Last modified Date:  2011-06-15
** Last Version:		1.0
** Descriptions:		
**
**------------------------------------------------------------------------------------------------------
** Modified by:
** Modified date:
** Version:
** Descriptions:
**
********************************************************************************************************/
#include "root.h"
#include "iap.h"
extern unsigned char dowfileflag;
extern unsigned char tranfile_flag;
extern int srv_echosend(UCHAR itf, faalpkt_t *pkt);
static int flash_eraseblk(ULONG addr, int size)
{
	ULONG pages = (size + FLASH_PAGE_SIZE -1) / FLASH_PAGE_SIZE;
	int i;

	for(i = 0; i < pages; i++)
	{
		FLASH_ErasePage(addr + i * FLASH_PAGE_SIZE);		
	}

	return 0;
}

static int flash_erase(UCHAR fileid)
{
	if(FILEID_PROGRAM == fileid)
	{
		return flash_eraseblk(DWN_PROGRAM_ADDR, DWN_PROGRAM_MAX);
	}
	
	return 1;
}

static int flash_program(ULONG flash_addr, UCHAR *ram_addr, int len)
{
	UCHAR *psrc = (UCHAR *)ram_addr;
	
//	if(len & 0x03) len = (len + 3) % 4;
	
	__disable_irq();

	WPREG_Disable();
     	FLASH_Unlock();
	
	while(len > 0)
	{
		FLASH_Program_M8(flash_addr, *psrc++);		
		flash_addr ++;
		len --;
	}

	FLASH_Lock();
     	WPREG_Enable();

	__enable_irq();

	return 0;
}


typedef struct {
	int timeid;

	USHORT serial;
	USHORT snmax;
	ULONG mask;
	ULONG pre_mask;
	ULONG filesize;

	ULONG version;

	UCHAR *pmem;
	UCHAR *pwrite;
	USHORT blksize;
	UCHAR winsize;
	UCHAR fileid;
	UCHAR blast;
	UCHAR itf;
} svrdfctl_t;

static svrdfctl_t g_svrdfctl = {-1, 0};

#define DWNFILE_MAGIC   0x2ac50630

static int check_program(svrdfctl_t *pfctl)
{
	return 0;
}

static int save_program(svrdfctl_t *pfctl)
{
	ULONG prog_addr, prog_max;

	if(FILEID_PROGRAM != pfctl->fileid)return 1;	

	prog_addr = MAIN_PROGRAM_ADDR;
	prog_max = MAIN_PROGRAM_MAX;

	return load_iapfunc(pfctl->pmem, pfctl->filesize, prog_addr, prog_max);
}

static int ctimer_dwnfile(ULONG arg)
{
	g_svrdfctl.timeid = -1;	
	g_svrdfctl.pmem = NULL;
	g_svrdfctl.version = 0;
	dowfileflag = 0;
	tranfile_flag = 0;
	print_logo(0,"clean dwnfile\r\n");
	return 1;
}

int svr_dwnfile_init(UCHAR itf)
{
	faalpkt_t *prcv = (faalpkt_t *)FAAL_RCVBUF(itf);
	faalpkt_t *psnd = (faalpkt_t *)FAAL_SNDBUF(itf);
	dwnfile_req_t *preq = (dwnfile_req_t *)(prcv->data);
	dwnfile_req_t *pecho = (dwnfile_req_t *)(psnd->data);
	svrdfctl_t *pctl = &g_svrdfctl;
	ULONG timeout;
	int len;
	len = (int)FAALAPP_LEN(prcv)&0xffff;
	if(len < sizeof(dwnfile_req_t)) return 1;

	*pecho = *preq;
	FAALAPP_LEN(psnd) = sizeof(dwnfile_req_t);
	psnd->ctrl = FAALCMD_REQFIRD;
	psnd->afn = FAALAFN_TRANFILE;

	if(DWNFILE_REQ_SN != make_long(preq->req)) goto MARK_DWNFILEINIT_FAIL;

	if(pctl->timeid >= 0)
	{
		stop_ctimer(pctl->timeid);
		pctl->timeid = -1;
	}

	if(NULL != pctl->pmem)
	{
		//os_free(pctl->pmem);
		pctl->pmem = NULL;
	}

	pctl->fileid = preq->idx;
	pctl->filesize = make_long(preq->size);
	pctl->serial = 0;
	pctl->mask = 0;
	pctl->winsize = preq->winsize;
	pctl->blksize = make_short(preq->blksize);
	pctl->version = 0;

	if((pctl->blksize < 50) || (pctl->blksize > FAAL_RCVMAX(itf))) goto MARK_DWNFILEINIT_FAIL;
	if((0 == pctl->winsize) || (pctl->winsize > WINNUM_FILEDATA)) goto MARK_DWNFILEINIT_FAIL;
	if(32 == pctl->winsize) pctl->pre_mask = 0xffffffff;
	else pctl->pre_mask = ((ULONG)1<<(pctl->winsize)) -1;
	switch(pctl->fileid)
	{
	case FILEID_PROGRAM:
		if(pctl->filesize > MAIN_PROGRAM_MAX) goto MARK_DWNFILEINIT_FAIL;
		flash_erase(FILEID_PROGRAM);
		pctl->pmem = (UCHAR *)DWN_PROGRAM_ADDR;
		break;
		
	default:
		goto MARK_DWNFILEINIT_FAIL;
	}
	
	//debug_print(0, "malloc address = %08XH\r\n", pctl->pmem);
	if(NULL == pctl->pmem)
	{
		//debug_print(0, "malloc %d fail\r\n", pctl->filesize);
		goto MARK_DWNFILEINIT_FAIL;
	}
	pctl->pwrite = pctl->pmem;

	timeout = make_long(preq->timeout);
	pctl->timeid = add_ctimer(timeout, ctimer_dwnfile, 0);
	if(pctl->timeid < 0)
	{
		pctl->pmem = NULL;
		goto MARK_DWNFILEINIT_FAIL;
	}

	pctl->snmax = pctl->filesize / pctl->blksize;
	if(pctl->filesize % pctl->blksize) pctl->snmax++;
	pctl->snmax--;
	pctl->blast = 0;
	pctl->version = make_long(preq->verison);
	pctl->itf = itf;

	print_logo(0, "start dwn file: len=%d, timeout=%d, snmax=%d\r\n", pctl->filesize, timeout, pctl->snmax);

	pecho->code = FILECODE_REQ;
	if(itf != FAALITF_RS485U)
	srv_echosend(itf, psnd);
	tranfile_flag = 1;
	dowfileflag = 1;
	return 0;

MARK_DWNFILEINIT_FAIL:
	pecho->code = FILECODE_REJECT;
	if(itf != FAALITF_RS485U)
	srv_echosend(itf, psnd);
	return 0;
}

static void dwnfile_memcpy(UCHAR fileid, UCHAR *dst, UCHAR *src, int len)
{
	if(FILEID_PROGRAM == fileid)
	{
		//print_logo(0, "Copy data to Flash addr: %08x\r\n", (ULONG)dst);
		flash_program((ULONG)dst, src, len);
	}		
}

int svr_dwnfile(UCHAR itf)
{
	faalpkt_t *prcv = (faalpkt_t *)FAAL_RCVBUF(itf);
	faalpkt_t *psnd = (faalpkt_t *)FAAL_SNDBUF(itf);
	dwnfile_t *pdwn = (dwnfile_t *)(prcv->data);
	dwnfile_t *pecho = (dwnfile_t *)(psnd->data);
	svrdfctl_t *pctl = &g_svrdfctl;	
	int len;
	ULONG offset, mask;
	UCHAR *puc;
	USHORT serial;

	len = (int)FAALAPP_LEN(prcv)&0xffff;
	if(len < LEN_DWNFILEHEAD) return 1;

	*pecho = *pdwn;
	FAALAPP_LEN(psnd) = LEN_DWNFILEHEAD;
	psnd->ctrl = FAALCMD_REQFIRD;
	psnd->afn = FAALAFN_TRANFILE;

	if(NULL == pctl->pmem)
	{
		pecho->code = FILECODE_REJECT;
		if(itf != FAALITF_RS485U)
		srv_echosend(itf, psnd);
		return 0;
	}
	len -= LEN_DWNFILEHEAD;

	{
		USHORT crc1, crc2;
		crc1 = cal_crc(pdwn->data, len);
		crc2 = make_short(pdwn->crc);
		if(crc1 != crc2) return 0;
	}

	serial = make_short(pdwn->serial);
//	print_logo(0, "%d,%d, %d, len=%d\r\n", serial, pctl->serial, pctl->snmax, len);
	if(serial > pctl->snmax) return 0;
	if((len < pctl->blksize) && (serial != pctl->snmax)) return 0;
	if(serial >= (pctl->serial+pctl->winsize)) return 0;
	if(serial < pctl->serial)
	{
		if(itf != FAALITF_RS485U)
		srv_echosend(itf, psnd);
		return 0;
	}
	clear_ctimer(pctl->timeid);

	if(serial == pctl->snmax)
	{
		pctl->blast = 1;
		offset = serial - pctl->serial;
		if(offset < 31)
		{
			mask = 1<<(offset+1);
			mask -= 1;
			pctl->pre_mask = mask;
		}

		//print_logo(0, "blast, mask=%08X, premask=%08X\r\n",pctl->mask, pctl->pre_mask);
	}
#if 0
	if(serial != pctl->serial){
		srv_echosend(itf, psnd);
		return 0;

	}
#endif	
	serial -= pctl->serial;
	mask = 1<<serial;
	offset = pctl->blksize;
	offset *= serial;
	puc = pctl->pwrite;
	puc += offset;
	
	dwnfile_memcpy(pctl->fileid, puc, pdwn->data, len);

	pctl->mask |= mask;

	if(pctl->mask == pctl->pre_mask)
	{
		if(pctl->blast)
		{
			int bchkok = 1;

			switch(pctl->fileid)
			{
			case FILEID_PROGRAM:
				if(check_program(pctl)) bchkok = 0;				
				break;

			default:
				bchkok = 0;
				break;
			}

			if(!bchkok)
			{				
				pctl->pmem = NULL;
				stop_ctimer(pctl->timeid);
				pctl->timeid = -1;
				return 1;
			}
			if(itf != FAALITF_RS485U)
			srv_echosend(itf, psnd);
			dowfileflag = 0;
			os_sleep(10);
			print_logo(0, "down file end\r\n");
			
			switch(pctl->fileid)
			{
			case FILEID_PROGRAM:				
				save_program(pctl);
				break;
				
			default:
				break;
			}

			pctl->pmem = NULL;
			stop_ctimer(pctl->timeid);
			pctl->timeid = -1;

			return 0;
		}

		pctl->serial += pctl->winsize;
		offset = pctl->winsize;
		offset *= pctl->blksize;
		pctl->pwrite += offset;
		pctl->mask = 0;
	}
	if(itf != FAALITF_RS485U)
	srv_echosend(itf, psnd);
	return 0;
}

